package com.ae.shield.common.block.tile;

import com.ae.shield.common.capability.energy.BlockEnergyStorage;
import com.ae.shield.common.recipe.assembly.AssemblyRecipe;
import com.ae.shield.common.recipe.ModRecipesType;
import com.ae.shield.common.recipe.assembly.IAssemblyRecipe;
import com.ae.shield.common.recipe.assembly.ShieldUpgradeAssemblyRecipe;
import com.ae.shield.common.gui.container.AssemblyContainer;
import com.ae.shield.common.items.shieldFittings.makers.ShieldMakerBase;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.server.SUpdateTileEntityPacket;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.NonNullList;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.templates.FluidTank;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;

public class AssemblyTile extends TileEntity implements ITickableTileEntity, INamedContainerProvider {
    private ItemStack lastItem = ItemStack.EMPTY;
    private int lastTick = 0;
    private int tick = 0;
    protected ItemStackHandler inventory = new ItemStackHandler(13);
    protected ItemStackHandler out = new ItemStackHandler(1);
    private final LazyOptional<IItemHandler> itemCap = LazyOptional.of(() -> inventory);
    private final LazyOptional<IItemHandler> itemOutCap = LazyOptional.of(() -> out);
    protected FluidTank tank = new FluidTank(10000);
    private final LazyOptional<IFluidHandler> fluidCap = LazyOptional.of(() -> tank);
    protected BlockEnergyStorage energy = new BlockEnergyStorage(10000);
    private final LazyOptional<IEnergyStorage> energyCap = LazyOptional.of(() -> energy);

    public AssemblyTile() {
        super(TileEntityList.ASSEMBLY_ENTITY.get());
    }

    @Nullable
    @Override
    public SUpdateTileEntityPacket getUpdatePacket() {
        return new SUpdateTileEntityPacket(pos, 1, getUpdateTag());
    }

    @Override
    public void onDataPacket(NetworkManager net, SUpdateTileEntityPacket pkt) {
        handleUpdateTag(world.getBlockState(pkt.getPos()), pkt.getNbtCompound());
    }

    @Nonnull
    @Override
    public CompoundNBT getUpdateTag() {
        return write(new CompoundNBT());
    }

    @Override
    public void handleUpdateTag(BlockState state, CompoundNBT tag) {
        super.handleUpdateTag(state, tag);
    }

    @Override
    public void tick() {
        if (!world.isRemote) {
            if (!inventory.getStackInSlot(0).isEmpty()) {
                tryCraft();
            }

            if (world.getGameTime() % 20 == 0) {
                world.notifyBlockUpdate(pos, getBlockState(), getBlockState(), Constants.BlockFlags.BLOCK_UPDATE);
            }
        }
    }

    private void tryCraft() {
        IAssemblyRecipe recipe = getRecipe();

        if (recipe == null) {
            if (initialize()) {
                markDirty();
            }
            return;
        }

        ItemStack recipeOutput = recipeOutput(recipe);

        if (lastItem.equals(recipeOutput, true)) {
            if (tick >= lastTick) {
                if (out.insertItem(0, lastItem, true).isEmpty()) {

                    if (lastItem.getItem() instanceof ShieldMakerBase) {
                        //所有产物是护盾的在输出前会进行一次护盾属性重载
                        ((ShieldMakerBase)lastItem.getItem()).reloadProperty(lastItem);
                    }

                    out.insertItem(0, lastItem, false);

                    for (int i = 0; i < inventory.getSlots(); i++) {
                        inventory.extractItem(i, 1, false);
                    }

                    tank.drain(new FluidStack(tank.getFluid().getFluid(), recipe.getFluidCount()), IFluidHandler.FluidAction.EXECUTE);
                    tick = 0;
                }
            } else {
                int energy = recipe.getEnergy();
                if (this.energy.extractEnergy(energy, true) == energy) {
                    this.energy.extractEnergy(energy, false);
                    ++tick;
                }
            }
        } else {
            lastItem = recipeOutput.copy();
            lastTick = recipe.getTick();
            tick = 0;
        }

        markDirty();
    }

    private ItemStack recipeOutput(IAssemblyRecipe recipe) {
        if (recipe instanceof ShieldUpgradeAssemblyRecipe) {
            //用于处理护盾进阶配方
            ShieldUpgradeAssemblyRecipe upgrade = (ShieldUpgradeAssemblyRecipe) recipe;
            short oldLevel = (short) upgrade.getOldLevel();
            short newLevel = (short) upgrade.getNewLevel();
            ItemStack stack = inventory.getStackInSlot(0).copy();
            ListNBT enchantments = stack.getOrCreateTag().getList("Enchantments", 10);
            boolean isDown = false;

            for (INBT enchantment : enchantments) {
                String id = ((CompoundNBT) enchantment).getString("id");
                short lvl = ((CompoundNBT) enchantment).getShort("lvl");
                if ("aeshield:level_up".equals(id) && lvl == oldLevel) {
                    ((CompoundNBT) enchantment).putShort("lvl", newLevel);
                    isDown = true;
                }
            }

            if (!isDown) {
                CompoundNBT compoundNBT = new CompoundNBT();
                compoundNBT.putShort("lvl", newLevel);
                compoundNBT.putString("id", "aeshield:level_up");
                enchantments.add(compoundNBT);
            }

            if (stack.getOrCreateTag().getList("Enchantments", 10).isEmpty()) {
                stack.getOrCreateTag().put("Enchantments", enchantments);
            }

            return stack;
        }else {
            return recipe.getRecipeOutput();
        }
    }

    @Nullable
    private IAssemblyRecipe getRecipe() {
        NonNullList<ItemStack> stacks = NonNullList.create();
        for (int i = 0; i < inventory.getSlots(); i++) {
            if (i == 0) {
                stacks.add(inventory.getStackInSlot(0));
            } else {
                ItemStack stackInSlot = inventory.getStackInSlot(i);
                if (!stackInSlot.isEmpty()) {
                    stacks.add(stackInSlot);
                }
            }
        }

        List<AssemblyRecipe> recipes = world.getRecipeManager().getRecipesForType(ModRecipesType.ASSEMBLY_TYPE);
        for (IAssemblyRecipe recipe : recipes) {
            if (recipe.matches(tank.getFluid(), stacks)) {
                return recipe;
            }
        }
        return null;
    }

    private boolean initialize() {
        if (!lastItem.equals(ItemStack.EMPTY) || lastTick != 0 || tick != 0) {
            lastItem = ItemStack.EMPTY;
            lastTick = 0;
            tick = 0;
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void read(@Nonnull BlockState state, @Nonnull CompoundNBT compoundNBT) {
        super.read(state, compoundNBT);
        lastTick = compoundNBT.getInt("LastTick");
        tick = compoundNBT.getInt("Tick");
        lastItem = ItemStack.read(compoundNBT.getCompound("LastItem"));
        inventory.deserializeNBT(compoundNBT.getCompound("Inventory"));
        out.deserializeNBT(compoundNBT.getCompound("Out"));
        tank.readFromNBT(compoundNBT);
        energy.setEnergy(compoundNBT.getInt("Energy"));
    }

    @Nonnull
    @Override
    public CompoundNBT write(@Nonnull CompoundNBT compoundNBT) {
        compoundNBT = super.write(compoundNBT);
        compoundNBT.putInt("LastTick", lastTick);
        compoundNBT.putInt("Tick", tick);
        compoundNBT.put("LastItem", lastItem.serializeNBT());
        compoundNBT.put("Inventory", inventory.serializeNBT());
        compoundNBT.put("Out", out.serializeNBT());
        tank.writeToNBT(compoundNBT);
        compoundNBT.putInt("Energy", energy.getEnergyStored());
        return compoundNBT;
    }

    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
        String capName = cap.getName();
        switch (capName.substring(capName.lastIndexOf(".") + 1)) {
            case "IItemHandler":
                if (side == Direction.DOWN) {
                    return itemOutCap.cast();
                }
                return itemCap.cast();
            case "IFluidHandler":
                return fluidCap.cast();
            case "IEnergyStorage":
                return energyCap.cast();
            default:
                return super.getCapability(cap, side);
        }
    }

    public IItemHandler getInventory() {
        return inventory;
    }

    public IItemHandler getOut() {
        return out;
    }

    public IFluidHandler getTank() {
        return tank;
    }

    public IEnergyStorage getEnergy() {
        return energy;
    }

    public int getLastTick() {
        return lastTick;
    }

    public int getTick() {
        return tick;
    }

    @Nonnull
    @Override
    public ITextComponent getDisplayName() {
        return new TranslationTextComponent("container.aeshield.assembly");
    }

    @Nullable
    @Override
    public Container createMenu(int id, @Nonnull PlayerInventory playerInventory, @Nonnull PlayerEntity player) {
        return new AssemblyContainer(id, playerInventory, pos);
    }
}
